#
# Copyright 2009 Red Hat, Inc. and/or its affiliates.
#
# Licensed to you under the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.  See the files README and
# LICENSE_GPL_v2 which accompany this distribution.
#

import os
from config import config
import logging
import tempfile
import threading

import iscsi
import nfs
import sd
import misc
import storage_exception as se


class StorageServerConnection:
    log = logging.getLogger('irs')
    storage_repository = config.get('irs', 'repository')

    @staticmethod
    def loggableConList(conList):
        cons = []
        for con in conList:
            conCopy = con.copy()
            for key in conCopy:
                if key.upper() == 'PASSWORD':
                    conCopy[key] = '******'
            cons.append(conCopy)
        return cons

    def connect(self, domType, spUUID, conList):
        """Connect to a storage low level entity (server).
        """
        self.log.info("(StorageServerConnection.connect) Request to connect %s storage server",
            sd.type2name(domType))
        if domType == sd.NFS_DOMAIN:
            return self.__connectNasServer(spUUID, conList)
        elif domType == sd.ISCSI_DOMAIN or domType == sd.FCP_DOMAIN:
            return self.__connectiSCSIServer(spUUID, conList)
        else:
            raise se.InvalidParameterException("type", domType)

    def disconnect(self, domType, spUUID, conList):
        """Disconnect from a storage low level entity (server).
        """
        self.log.info("(StorageServerConnection.disconnect) Request to disconnect %s storage server",
            sd.type2name(domType))
        if domType == sd.NFS_DOMAIN:
            return self.__disconnectNasServer(spUUID, conList)
        elif domType == sd.ISCSI_DOMAIN or domType == sd.FCP_DOMAIN:
            return self.__disconnectiSCSIServer(spUUID, conList)
        else:
            raise se.InvalidParameterException("type", domType)

    def validate(self, domType, spUUID, conList):
        """Validate that we can connect to a storage server.
        """
        self.log.info("(StorageServerConnection.validate) Request to validate %s storage server",
            sd.type2name(domType))
        if domType == sd.NFS_DOMAIN:
            return self.__validateNasConnection(spUUID, conList)
        elif domType == sd.ISCSI_DOMAIN or domType == sd.FCP_DOMAIN:
            return self.__validateiSCSIConnection(spUUID, conList)
        else:
            raise se.InvalidParameterException("type", domType)

    def __connectNasServer(self, spUUID, conList):
        """Connect to a storage low level entity (server).
            'spUUID' - storage pool UUID
            'conList' - [{'id','connection':'server:/export_path','portal',
                        'port','iqn','user','password'},...]
        """
        cons = self.loggableConList(conList=conList)
        self.log.info("(StorageServerConnection.__connectNasServer) spUUID=%s conList=%s",
            spUUID, cons)
        conStatus = []

        # create local mount point
        localPath = os.path.join(self.storage_repository, sd.DOMAIN_MNT_POINT)
        if not (os.path.exists(localPath) or nfs.isStaleHandle(localPath)):
            os.mkdir(localPath)

        for con in conList:
            try:
                cid = con['id']
                rp = con['connection']
            except KeyError:
                raise se.InvalidParameterException("connectionsList", cons)

            try:
                mntPoint = nfs.transformPath(rp)
                mntPath = os.path.join(localPath, mntPoint)
                # Stale handle usually resolves itself when doing directory lookups
                # BUT if someone deletes the export on the servers side. We will keep
                # getting stale handles and this is unresolvable unless you umount and
                # remount.
                if nfs.isStaleHandle(mntPath):
                    nfs.umount(rp, mntPath)

                if not os.path.exists(mntPath):
                    os.mkdir(mntPath)
                rc = nfs.mount(rp, mntPath)
                if rc == 0:
                    try:
                        nfs.validateAccess(mntPath)
                    except se.StorageServerAccessPermissionError, ex:
                        self.log.debug("Unmounting NFS server %s "
                            "(not enough access permissions)" % rp)
                        nfs.umount(rp, mntPath)
                        raise
                else:
                    self.log.error("Error during storage connection: rc=%s", rc)
                    rc = se.StorageServerConnectionError.code

            # We should return error status instead of exception itself
            except se.StorageException, ex:
                rc = ex.code
                misc.logException(ex)
            except Exception, ex:
                rc = se.StorageServerConnectionError.code
                misc.logException(ex)

            conStatus.append({'id':cid, 'status': rc})
        return conStatus

    def __connectiSCSIServer(self, spUUID, conList):
        """Connect to a storage low level entity (server).
        """
        cons = self.loggableConList(conList=conList)
        self.log.info("(StorageServerConnection.__connectiSCSIServer) spUUID=%s conList=%s",
            spUUID, cons)
        conStatus = []
        parsedConList = []

        for con in conList:
            try:
                cid = con['id']
                ip = con['connection']
                port = con['port']
                iqn = con['iqn']
                tpgt = con['portal']
                user = con['user']
                password = con['password']
                parsedConList.append([cid, ip, port, iqn, tpgt, user, password])
            except KeyError:
                conStatus.append({'id': cid, 'status': se.InvalidParameterException.code})

        sessionCheckList = iscsi.checkSessionList(parsedConList)
        for i, exists in enumerate(sessionCheckList):
            cid, ip, port, iqn, tpgt, user, password = parsedConList[i]
            if exists:
                conStatus.append({'id': cid, 'status': 0})
                continue
            try:
                if not iqn:
                    rc = iscsi.addiSCSIPortal(ip, port, user, password)[0]
                else:
                    rc = iscsi.addiSCSINode(ip, port, iqn, tpgt, user, password)


            # We should return error status instead of exception itself
            except se.StorageException, ex:
                rc = ex.code
                misc.logException(ex)
            except Exception, ex:
                rc = se.StorageException.code
                misc.logException(ex)

            conStatus.append({'id':cid, 'status':rc})

        return conStatus

    def __validateNasConnection(self, spUUID, conList):
        """Validate that we can connect to a storage server.
        """
        cons = self.loggableConList(conList=conList)
        self.log.info("(StorageServerConnection.__validateNasConnection) spUUID=%s conList=%s",
            spUUID, cons)
        conStatus = []

        threads = []

        def handleConnection(con):
            try:
                try:
                    cid = con['id']
                    rp = con['connection']
                except KeyError:
                    raise se.InvalidParameterException("connectionsList", cons)

                mountpoint = tempfile.mkdtemp()
                try:
                    rc = nfs.mount(rp, mountpoint)
                    if rc == 0:
                        try:
                            nfs.validateAccess(mountpoint)
                        except se.StorageServerAccessPermissionError, ex:
                            rc = ex.code
                    else:
                        self.log.error("Error during storage connection validation: rc=%s", rc)
                        rc = se.StorageServerValidationError.code
                finally:
                    try:
                        nfs.umount(rp, mountpoint)
                    finally:
                        os.rmdir(mountpoint)

            except se.StorageException, ex:
                rc = ex.code
                misc.logException(ex)
            except Exception, ex:
                rc = se.StorageServerValidationError.code
                misc.logException(ex)

            conStatus.append({'id':cid, 'status': rc})

        for con in conList:
            thread = threading.Thread(target=handleConnection, args=[con])
            thread.start()
            threads.append(thread)

        for thread in threads:
            thread.join()

        return conStatus

    def __validateiSCSIConnection(self, spUUID, conList):
        """Validate that we can connect to a storage server.
        """
        cons = self.loggableConList(conList=conList)
        self.log.info("(StorageServerConnection.__validateiSCSIConnection) spUUID=%s conList=%s",
            spUUID, cons)
        conStatus = []

        for con in conList:
            try:
                cid = con['id']
                ip = con['connection']
                port = con['port']
                iqn = con['iqn']
                tpgt = con['portal']
                user = con['user']
                password = con['password']
            except KeyError:
                raise se.InvalidParameterException("connectionsList", cons)
            #iscsi.addiSCSIPortal('localhost')  ##FIXME
            conStatus.append({'id':cid, 'status':0})
        return conStatus

    def __disconnectNasServer(self, spUUID, conList):
        """Disconnect from a storage low level entity (server).
        """
        cons = self.loggableConList(conList=conList)
        self.log.info("(StorageServerConnection.__disconnectNasServer) spUUID=%s conList=%s",
            spUUID, cons)
        conStatus = []

        for con in conList:
            try:
                cid = con['id']
                rp = con['connection']
            except KeyError:
                raise se.InvalidParameterException("connectionsList", cons)

            try:
                localPath = os.path.join(self.storage_repository,
                    sd.DOMAIN_MNT_POINT)
                mntPoint = nfs.transformPath(rp)
                mntPath = os.path.join(localPath, mntPoint)
                rc = nfs.umount(rp, mntPath)
                if rc == 0:
                    try:
                        os.rmdir(mntPath)
                    except OSError, e:
                        # Report the error to the log, but keep going,
                        # afterall we succeeded to disconnect the NAS server
                        msg = ("(StorageServerConnection.__disconnectNasServer)"
                            " Cannot remove mountpoint after umount()")
                        self.log.warning(msg)
                        misc.logException(e)
                else:
                    self.log.error("Error during storage disconnection: rc=%s", rc)
                    rc = se.StorageServerDisconnectionError.code
            # We should return error status instead of exception itself
            except se.StorageException, ex:
                rc = ex.code
                misc.logException(ex)
            except Exception, ex:
                rc = se.StorageServerDisconnectionError.code
                misc.logException(ex)

            conStatus.append({'id':cid, 'status': rc})
        return conStatus

    def __disconnectiSCSIServer(self, spUUID, conList):
        """Disconnect from a storage low level entity (server).
        """
        cons = self.loggableConList(conList=conList)
        self.log.info("(StorageServerConnection.__disconnectiSCSIServer) spUUID=%s conList=%s",
            spUUID, cons)
        conStatus = []

        for con in conList:
            try:
                cid = con['id']
                ip = con['connection']
                port = con['port']
                iqn = con['iqn']
                tpgt = con['portal']
                user = con['user']
                password = con['password']
            except KeyError:
                raise se.InvalidParameterException("connectionsList", cons)

            try:
                if not iqn:
                    rc = iscsi.remiSCSIPortal(ip, port)
                else:
                    rc = iscsi.remiSCSINode(ip, port, iqn, tpgt)
            # We should return error status instead of exception itself
            except se.StorageException, ex:
                rc = ex.code
                misc.logException(ex)
            except Exception, ex:
                rc = se.StorageException.code
                misc.logException(ex)

            conStatus.append({'id':cid, 'status':rc})

        return conStatus
